package algo.math.evaluator.tokenizer;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import algo.math.evaluator.base.exceptions.InvalidToken;
import algo.math.evaluator.context.EvaluationContext;
import algo.math.evaluator.context.IComponentObject;
import algo.math.evaluator.context.functions.interfaces.*;
import algo.math.evaluator.context.operator.Operator;
import algo.math.evaluator.context.operator.interfaces.IOperator;
import algo.math.evaluator.exceptions.MaxTokenException;
import algo.math.evaluator.exceptions.SyntaxException;
import algo.math.evaluator.expression.Expression;
import algo.math.evaluator.tokenizer.interfaces.*;
import algo.math.evaluator.tokenizer.interfaces.IToken.OperatorSymbol;
import algo.math.evaluator.tokenizer.interfaces.IToken.TokenType;

public class Tokenizer {

	public Expression tokenize(String exp, Map<String, Object> variables) throws Exception, InvalidToken
	{
			
			if (null==variables)
				variables = new Hashtable<String, Object>();
			
			Boolean flag = false;
			char invalidToken = 0;
            List<IToken> tokens = new ArrayList<IToken>();
            exp = exp.replace(" ", "");
            int index=0;
            int count=0;
            
            StringBuffer expression_ = new StringBuffer();
            StringBuffer alphaNumericString = new StringBuffer();
            expression_.append(exp);
           
            
            while (index<expression_.length())
            {
            	
                char currentChar = expression_.charAt(index);

                if (
                		!EvaluationContext.GetInstance().getBase().isBaseToken(currentChar)&&
                		!variables.keySet().contains(String.valueOf(currentChar)) &&
                		!EvaluationContext.GetInstance().registredOperatorsSymbols().contains(String.valueOf(currentChar)) &&
                		!(currentChar==','||currentChar=='.')
                	)
                {
                	flag = true;
                	invalidToken = currentChar;
                }
                
                if (EvaluationContext.GetInstance().getComponentById(IOperator.class, Character.toString(currentChar))!= null)
                {
                    if (alphaNumericString.length() > 0)
                    {
                        tokens.add( (IToken)Token.resolve(alphaNumericString.toString()));
                        if (count++==Integer.MIN_VALUE)
                        	throw new MaxTokenException("Maximum number of tokens reached");
                        //alphaNumericString =""
                        alphaNumericString = new StringBuffer();
                    }
                    
                    Operator op = (Operator) EvaluationContext.GetInstance().getComponentById(IOperator.class, Character.toString(currentChar));
             
                    if (op.getName().equals(OperatorSymbol.MINUS.toString()))
                    {
                    	if (tokens.size()>0)
                    	{
                    	if (tokens.get(tokens.size()-1).getType().equals(TokenType.OPERATOR))
	                    	{
                    		alphaNumericString.append(currentChar);
                    		//alphaNumericString+=currentChar;
	                    	}
                    	else{
                    		tokens.add(op);
                            if (count++==Integer.MIN_VALUE)
                            	throw new MaxTokenException("Maximum number of tokens reached");
                    	}
                    	
                    	}
                    	else {
                    		alphaNumericString.append(currentChar);
                    		//alphaNumericString+=currentChar;
                    	}
                    	
                    }
                    
                    else
                    {
                    	tokens.add(op);
                        if (count++==Integer.MIN_VALUE)
                        	throw new MaxTokenException("Maximum number of tokens reached");
                    }
                    
                }
                
                else if (Character.isLetter(currentChar) 
                		&&(!EvaluationContext.GetInstance().getSeparators().contains(currentChar) )
                		)
                {
                	Boolean isFunction= false;
                	
                	for (IComponentObject f :  EvaluationContext.GetInstance().getComponents(IFunction.class,EvaluationContext.TYPE_COMPONENT.REGISTERED)) {
                		
                		if (f.getComponentName().length() +index <= expression_.length())
                		{
                			if (expression_.substring(index,index+ f.getComponentName().length()).toLowerCase().equals(f.getComponentName().toLowerCase()))
                			{
                				index+=f.getComponentName().length();
                				
                				alphaNumericString = new StringBuffer();
                				alphaNumericString.append(f.getComponentName());
                				flag = false;
                			 	
                				while (!(String.valueOf(expression_.charAt(index)).equals(((Operator) EvaluationContext.GetInstance().getComponentByName(IOperator.class, OperatorSymbol.OPEN_PARENTHESIS.toString())).getValue()))) //.getOperator(OperatorSymbol.OPEN_PARENTHESIS.toString()).getValue())))	
         
                				{
                							if (index == expression_.length())
                							{
                								throw new SyntaxException("Syntax error");
                							}
                							index++;	
                				}
                				//alphaNumericString +=((Operator) EvaluationContext.GetInstance().getComponentByName(IOperator.class, OperatorSymbol.OPEN_PARENTHESIS.toString())).getValue();
                				alphaNumericString.append(((Operator) EvaluationContext.GetInstance().getComponentByName(IOperator.class, OperatorSymbol.OPEN_PARENTHESIS.toString())).getValue());
                				index++;
                				while (!(String.valueOf(expression_.charAt(index)).equals(((Operator) EvaluationContext.GetInstance().getComponentByName(IOperator.class, OperatorSymbol.CLOSE_PARENTHESIS.toString())).getValue()))) //.getOperator(OperatorSymbol.OPEN_PARENTHESIS.toString()).getValue())))	
                				{
		                					if (index == expression_.length())
		        							{
		                						throw new SyntaxException("Syntax error");
		        							}
		                					alphaNumericString.append(String.valueOf(expression_.charAt(index)));
		                					//alphaNumericString +=String.valueOf(expressionString.charAt(index));
		        							index++;
                				}
                				alphaNumericString.append(((Operator) EvaluationContext.GetInstance().getComponentByName(IOperator.class, OperatorSymbol.CLOSE_PARENTHESIS.toString())).getValue());
                				
                				//alphaNumericString +=((Operator) EvaluationContext.GetInstance().getComponentByName(IOperator.class, OperatorSymbol.CLOSE_PARENTHESIS.toString())).getValue();
                				tokens.add(new Token(alphaNumericString.toString(),TokenType.FUNCTION));
                                if (count++==Integer.MIN_VALUE)
                                	throw new MaxTokenException("Maximum number of tokens reached");
                				alphaNumericString = new StringBuffer();
                				//alphaNumericString = "";
                				break;
                			}
                		}
					}
                	
                	if (
                			!isFunction && 
                			! (EvaluationContext.GetInstance().getComponentById(IOperator.class,Character.toString(currentChar))!= null)&&
                			!(EvaluationContext.GetInstance().getBase().isBaseToken(currentChar))	
                	   )
                	{
                		for (String var : variables.keySet()) 
                		{
                    		if (var.length() +index <= expression_.length())
                    		{
                    			if (expression_.substring(index,index+ var.length()).toLowerCase().equals(var))
                    			{
                    				if (var.length()>1)index+=(var.length()-1);
                    				tokens.add(new Token(var,TokenType.VARIABLE));
                                    if (count++==Integer.MIN_VALUE)
                                    	throw new MaxTokenException("Maximum number of tokens reached");
                    				break;
                    			}
                    		}
						}
                	}
                	
                	if (EvaluationContext.GetInstance().getBase().isBaseToken(currentChar))
                			{
                    	if (currentChar == ',')currentChar = '.';
                    	alphaNumericString.append(currentChar);
                    	//alphaNumericString += currentChar;
                		
                			}
                	
                }
                else if (EvaluationContext.GetInstance().getBase().isBaseToken(currentChar)|| Character.isDigit(currentChar)||  (currentChar == '.' || currentChar == ',') )
                {
                	if (currentChar == ',')currentChar = '.';
                	alphaNumericString.append(currentChar);
                	//alphaNumericString += currentChar;
                }
                
                
                else if (
                		String.valueOf(expression_.charAt(index)).equals(((Operator) EvaluationContext.GetInstance().getComponentByName(IOperator.class, OperatorSymbol.OPEN_PARENTHESIS.toString())).getValue()) ||
                		String.valueOf(expression_.charAt(index)).equals(((Operator) EvaluationContext.GetInstance().getComponentByName(IOperator.class, OperatorSymbol.CLOSE_PARENTHESIS.toString())).getValue())
                )
                {
                	
                   Operator op = (Operator) EvaluationContext.GetInstance().getComponentById(IOperator.class, Character.toString(currentChar));
                    tokens.add(op);
                    if (count++==Integer.MIN_VALUE)
                    	throw new MaxTokenException("Maximum number of tokens reached");
                }
                index++;
            }

            if (alphaNumericString.length() > 0)
            {
                tokens.add((IToken) Token.resolve(alphaNumericString.toString()));
                if (count++==Integer.MIN_VALUE)
                	throw new MaxTokenException("Maximum number of tokens reached");
            }
            
            Expression r = new Expression(tokens,variables,EvaluationContext.GetInstance().getBase());
            
            if (flag == true)
            	throw new InvalidToken("Invalid token : " + invalidToken);
            
            
            return r;
        }

}